home *** CD-ROM | disk | FTP | other *** search
/ PC Media 22 / PC MEDIA CD22.iso / share / prog / datalib2 / dataexe1.cpp < prev    next >
C/C++ Source or Header  |  1995-08-15  |  17KB  |  662 lines

  1. /*
  2.  
  3. INTRODUCTION
  4.  
  5.   This is a generic expression evaluator which is intended to be used
  6.   by any program which requires one.
  7.  
  8.   It is intended to be expandable and allows the use of variables by user
  9.   written routines which can be modified according to the specific program
  10.   being used. An error routine may also be specified.
  11.   
  12.   Users are expected to call two routines externally, the first is the
  13.   initialisation routine which reads a data file (which may form part of
  14.   a larger data file from the external program) and which should only be
  15.   called once, the second is the expression evaluator which takes a string
  16.   and returns an integer, it will also update the string so that the first
  17.   character not forming part of the expression is returned.
  18.   
  19.   Strings may be passed in non-tokenised form which is slow, or may be
  20.   tokenised before being passed which results in faster evaluation. There
  21.   is a tokeniser routine which may be called externally to tokenise
  22.   expressions in loaders or compilers etc.
  23.   
  24.   The evaluator allows the use of functions, constants, and unary and binary
  25.   operators.
  26.   
  27. ROUTINES to be used are as follows:
  28.   
  29.   exeinit    initialises the evaluator, reads operator data file,
  30.           grabs memory as required.
  31.           
  32.   exetoken    Takes an ascii string and produces a tokenised output,
  33.                 it is not essential to tokenise strings as the evaluator can
  34.                 do it for you.
  35.                 
  36.   exeval    Evaluate an expression. Pass a string pointer to a pointer
  37.                 containing the expression and the result is returned as 
  38.                 an int. The string pointer is updated to point to the first
  39.                 character after the expression whether it is tokenised or 
  40.                 not.
  41.                 
  42.                 The routine returns a pointer to an operand structure which
  43.                 indicates the type of return (string or integer), and contains
  44.                 the return value, which in the case of a string is the pointer
  45.                 to it.
  46.  
  47. ROUTINES to be provided by the user
  48.  
  49.   void errfatal(char *)
  50.       
  51.           fatal error handler must be written by the user, and must
  52.           exit the program printing the supplied string.
  53.   
  54.   void err(char *)    
  55.   
  56.           error handler, non fatal, must be written by the user, and
  57.           return to the calling routine.
  58.           
  59.   struct op *getvar(char *)
  60.   
  61.           Get a variable pointed to by the string and return its value,
  62.           getvar should contain a local static op structure whose
  63.           address is returned.
  64.           
  65. EXPANDING the program
  66.  
  67.   The program may be expanded or modified to handle different functions or
  68.   existing functions in a different way.
  69.  
  70.   Constants may be added without changing the program at all, simply changing
  71.   EXOP.DAT
  72.   
  73.   EXOP.DAT which may be incorporated in a user data file must be modified,
  74.   the information contained in it is the string, priority, type and number
  75.   of each operator. The number of the operator may be substituted below.
  76.  
  77.   Operators and functions are dealt with in the body of the evalstr routine.
  78.   
  79.   
  80. */
  81.  
  82. #include "datapriv.hpp"
  83.  
  84. /*
  85.  
  86.  This file is the data required for the expression evaluator.
  87. */
  88.  
  89. /*
  90.  
  91.  This file contains data in the form
  92.  
  93.  LITERAL,TYPE,PRIORITY,TYPE of 1st OP,TYPE of 2nd OP,Type Result,No.
  94.  
  95.  Type is : 0, Constant (pri=value)
  96.            1, Unary operator
  97.            2, Binary Operator
  98.            3, Both types (e.g. -)
  99.          >=4, Function, PRI=no. of parameters
  100.  
  101.  Note for a function the type represents the number of optional
  102.  parameters, so 1 optional parameter has a type of 5, optional
  103.  parameters with no value supplied are inserted as integer -1.
  104.  
  105.  Type-1 & Type-2 are the types of operand, 1=num, 2=string, 4=Date, 8=Logical
  106.  
  107. LIT    TYPE    PRI     Type-1  Type-2  Type-Result 
  108.  
  109. */
  110.  
  111. struct opinf oppt[]=
  112. {
  113.  "(",4,9,0,0,0,0,
  114.  ")",4,9,0,0,0,1,
  115.  "+",3,5,7,7,1,2,
  116.  "-",3,5,7,7,1,3,
  117.  "**",2,7,1,1,1,4,
  118.  "*",2,6,1,1,1,5,
  119.  "/",2,6,1,1,1,6,
  120.  ".AND.",2,2,8,8,8,7,
  121.  ".OR.",2,1,8,8,8,8,
  122.  ".NOT.",1,3,8,8,8,9,
  123.  "=",2,4,15,15,8,10,
  124.  "<>",2,4,15,15,8,11,
  125.  ">=",2,4,7,7,8,12,
  126.  "<=",2,4,7,7,8,13,
  127.  ">",2,4,7,7,8,14,
  128.  "<",2,4,7,7,8,15,
  129.  "$",2,4,2,2,8,16,
  130.  "ABS",4,1,1,0,1,17,
  131.  "ASC",4,1,2,0,1,18,
  132.  "AT",4,2,2,2,1,19,
  133.  "CDOW",4,1,4,0,2,20,
  134.  "CHR",4,1,1,0,2,21,
  135.  "CMONTH",4,1,4,0,2,22,
  136.  "CTOD",4,1,2,0,4,23,
  137.  "DATE",4,0,0,0,4,24,
  138.  "DAY",4,1,4,0,1,25,
  139.  "DOW",4,1,4,0,1,26,
  140.  "DTOC",4,1,4,0,2,27,
  141.  "DTOS",4,1,4,0,2,28,
  142.  "IIF",4,3,8,15,0,29,
  143.  "INT",4,1,1,0,1,30,
  144.  "ISALPHA",4,1,2,0,8,31,
  145.  "ISDIGIT",4,1,2,0,8,32,
  146.  "ISLOWER",4,1,2,0,8,33,
  147.  "ISUPPER",4,1,2,0,8,34,
  148.  "LEFT",4,2,2,1,2,35,
  149.  "LEN",5,1,2,0,1,36,
  150.  "LOWER",4,1,2,0,2,37,
  151.  "LTRIM",4,1,2,0,2,38,
  152.  "MAX",4,2,5,5,0,39,
  153.  "MIN",4,2,5,5,0,40,
  154.  "MOD",4,2,1,1,1,41,
  155.  "MONTH",4,1,4,0,1,42,
  156.  "RECCOUNT",4,0,0,0,1,43,
  157.  "RECNO",4,0,0,0,1,44,
  158.  "RECSIZE",4,0,0,0,1,45,
  159.  "REPLICATE",4,2,2,1,2,46,
  160.  "RIGHT",4,2,2,1,2,47,
  161.  "ROUND",4,2,1,1,1,48,
  162.  "RTRIM",4,1,2,0,2,49,
  163.  "SOUNDEX",4,1,2,0,2,50,
  164.  "SPACE",4,1,1,0,2,51,
  165.  "STR",6,1,1,1,2,52,
  166.  "STUFF",4,4,2,1,2,53,
  167.  "SUBSTR",5,2,2,1,2,54,
  168.  "SWAPDATA",4,1,2,0,2,55,
  169.  "TIME",4,0,0,0,2,56,
  170.  "TRIM",4,1,2,0,2,57,
  171.  "TYPE",4,1,15,0,0,58,
  172.  "UPPER",4,1,2,0,2,59,
  173.  "VAL",4,1,2,0,1,60,
  174.  "YEAR",4,1,4,0,1,61,
  175.  "^",2,7,1,1,1,62,
  176.  "#",2,4,15,15,8,63
  177. };
  178.  
  179.  
  180. // Number of operators
  181.  
  182. int nop=64;
  183.  
  184. // Lookup table for end of array and spaces
  185. // bit 0=space, bit 1=end of expr.
  186.  
  187. // End chars are 0 \n , : ; ) '
  188.  
  189. // Space chars are 32 and 9
  190.  
  191. char sparray[]=
  192. {
  193. // 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f
  194.  
  195.    2,0,0,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00-1f
  196.    1,0,0,0,0,0,0,2,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0, // 20-3f
  197.    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40-5f
  198.    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0  // 60-7f
  199. };
  200.  
  201.  
  202. /**************************************************************************
  203.  
  204.    Initialisation routine, this takes a file pointer and loads the file of 
  205.    operator definitions. This file is by default included in EXOP.DAT.
  206.    The format of the file is as follows:
  207.    
  208.    
  209.    #
  210.    STRING,TYPE,PRIORITY,TYPE1,TYPE2,NUMBER
  211.    STRING,TYPE,PRIORITY,TYPE1,TYPE2,NUMBER
  212.    .
  213.    .
  214.    .
  215.    #
  216.    
  217.    Characters up to the first '#' are ignored, there should be no spurious
  218.    spaces or tabs within the data.
  219.    
  220.    STRING is the literal for the operator, e.g. +,-,AND.
  221.    
  222.    TYPE is the type, 0=constant, 1=Unary op, 2=Binary op.,
  223.                      3=binary or unary op, 4=function.
  224.  
  225.    PRIORITY for a BINARY operator is the priority, higher number=higher priority
  226.                   (the priority must be greater than 0).
  227.            for a CONSTANT this number is the value of the constant.
  228.            for a FUNCTION this number is the number of arguments.
  229.            
  230.    TYPE1    is the type of the single operand for functions and unary operators
  231.             and the type of first operand (the left operand) for binary. Type 
  232.             is 1 for integer, 2 for string or 3 for either.
  233.             
  234.    TYPE2    only has meaning for binary operators where it is the type of the
  235.            second operand (after the operator).
  236.  
  237.    NUMBER is the index of the operator and should be used by the tokeniser.
  238.    
  239.    This also initialises SPARRAY, which contains special character information
  240.    as follows:
  241.    
  242.    If a character (n) is not special then sparry[n]=0;
  243.    
  244.    Bit 0 of sparray[n]=space character.
  245.    Bit 1 of sparray[n]=end of expression character.
  246.    
  247. ***************************************************************************/
  248.  
  249.  
  250. expval::expval(database *dbi)
  251. {
  252.  erflag=0;
  253.  *(dbi->ers)=0;
  254.  db=dbi;
  255.   
  256.  if (!(exwork=new char[EXWORKSIZE]))
  257.   errfatal("No memory for expression evaluator information");
  258. }
  259.  
  260. // Destructor
  261.  
  262. expval::~expval()
  263. {
  264.  if (exwork) delete exwork;
  265. }
  266.  
  267.  
  268. /**************************************************************************
  269.  
  270.  Tokeniser
  271.  
  272.  This routine tokenises an input string and passes the result to an output
  273.  string, it is useful for input scanners, but is also used by the expression
  274.  evaluator when an untokenised expression is passed.
  275.  
  276.  The tokenised expression consists of a number of fields each of which starts
  277.  with a byte:
  278.  
  279.  EXSTART    Indicates the 1st byte of the expression
  280.  EXINT        Indicates an integer follows in 2 bytes.
  281.  EXVAR        Indicates a variable, followed by the variable name which is 
  282.           terminated by a 0 byte.
  283.  EXSTR        A zero terminated string.
  284.  EXEND        Last byte of expression.
  285.  >=0        Indicates an operator, consists of the operator index.
  286.  
  287.  It speeds up the evaluator by pre-evaluating constants and integers.
  288.  
  289.  It should be passed two strings : s1 and s2
  290.  
  291.  s1 is a pointer to the input string
  292.  s2 is a pointer to the output string which the tokenised result is written to
  293.  
  294.  The routine returns a pointer to the 1st character which is not in the
  295.  expression. The expression is taken to end with one of the four EXEND
  296.  characters defined in the header file.
  297.  
  298.  
  299. **************************************************************************/
  300.  
  301. char *expval::exetoken(char *s1,char **s2)
  302. {
  303.  char *ls1=s1,*ls2=*s2;        // Pointer to next free byte in s2
  304.  char ws[128],*wsp;        // Useful space
  305.  int c,i;
  306.  
  307.  *ls2++=EXSTART;    /* Write in the expression flag */
  308.  
  309.  c=exescan(&ls1);    // Get next character
  310.  
  311.  do
  312.  {
  313.   if (isdigit(c) || c==34 || (c=='.' && isdigit(*ls1))) // Number/String ? 
  314.   {
  315.    if (c==34)      /* A string */
  316.    {
  317.     *ls2++=EXSTR; c=exescan(&ls1,1);
  318.     while(c!=34 && c) {*ls2++=c; c=exescan(&ls1,1);} *ls2++=0;
  319.     if (c==34) c=exescan(&ls1);
  320.    }
  321.    else      /* A number */
  322.    {
  323.     wsp=ws; 
  324.     while(isdigit(c) || (c=='.'&& isdigit(*ls1))) {*wsp++=c; c=exescan(&ls1);}
  325.     *wsp=0; *ls2++=EXINT; (*(double *)ls2)=atof(ws); ls2+=sizeof(double);
  326.    }
  327.   }
  328.   else      /* expression or a variable */
  329.   {
  330.    if ((i=exegetop(c,&ls1))!=-1)        /* Have we found an expression */
  331.    {
  332.     if (i==OBRAC)
  333.     {
  334.      ls1=exetoken(ls1,&ls2);
  335.      if (*ls1!=')') {err("Unmatched Bracket !"); c=0;}
  336.      else {ls1++; c=exescan(&ls1);}
  337.     }
  338.     else    // Operator or function, not an open bracket
  339.     {
  340.      c=exescan(&ls1);
  341.      if (oppt[i].type==CONSTANT)
  342.         {*ls2++=EXINT; (*(double *)ls2)=oppt[i].pri; ls2+=sizeof(double);}
  343.      else
  344.      {
  345.       *ls2++=i;            // Some kind of operator
  346.       if (oppt[i].type>=FUNCTION)        // A function
  347.       {
  348.        if (c!='(') {err("Need a \'(\' for a function"); c=0;}
  349.        else
  350.        {
  351.         for(int j=oppt[i].pri; j>0; j--) // Get n-1 parameters, ending in ,
  352.         {
  353.          ls1=exetoken(ls1,&ls2);
  354.          if (j>1)            // expect , between parameters
  355.          {
  356.           exescan(&ls1);
  357.           if (*ls1!=',') {err("Expected a \',\'"); c=0; j=0;}
  358.           else ls1++;
  359.          }
  360.         }
  361.         for(j=0; j<oppt[i].type-4; j++)            // Optional parameters
  362.         {
  363.          if (*ls1==',') {ls1++; ls1=exetoken(ls1,&ls2);}
  364.          else {*ls2++=EXSTART; *ls2++=EXINT; (*(double *)ls2)=-1; 
  365.                ls2+=sizeof(double); *ls2++=EXEND;}
  366.         }
  367.         exescan(&ls1);
  368.         if (*ls1!=')') {err("Needs a \')\' !"); c=0;}
  369.         else {ls1++; c=exescan(&ls1);}
  370.        }
  371.       }
  372.      }
  373.     }
  374.    }
  375.    else                   /* No expression, field ? */
  376.    {
  377.     if (isalpha(c))
  378.     {
  379.      field *f;
  380.      wsp=ws;
  381.      while(isalpha(c) || c=='_') {*wsp++=c; c=exescan(&ls1);} *wsp++=0;  /* field */
  382.      f=db->getfield(ws);
  383.      if (!f) {err("Invalid field"); c=0;}
  384.      else {*ls2++=EXVAR; *(int *)ls2=f->number; ls2+=sizeof(int);}
  385.     }
  386.     else
  387.     {err("Unrecognised Expression"); c=0;}
  388.    }
  389.   }
  390.  }
  391.  while(c);
  392.  
  393.  *ls2++=EXEND;
  394.  *s2=ls2;
  395.  
  396.  return(ls1);
  397. }
  398.  
  399. // Scan input line up to next space, or end of line
  400. // sflag is 1 if we are reading within inverted commas ("")
  401.  
  402. int expval::exescan(char **in,int sflag)
  403. {
  404.  int c;
  405.  
  406.  if (sflag) {if (**in=='\n' || !(**in)) return 0;}
  407.  else {if (sparray[**in] & 2) return 0;}
  408.  
  409.  if (sflag) c=**in;
  410.  else
  411.  {
  412.   while(sparray[**in] & 1) (*in)++;    /* Eliminate leading spaces */
  413.  
  414.   c=(islower(**in)) ? **in-('a'-'A'): **in;
  415.  }
  416.  
  417.  (*in)++;
  418.  return c;
  419. }
  420.  
  421. /* Check supplied string to see if it is an operator */
  422. // c is the first character
  423.  
  424. int expval::exegetop(int c,char **s)
  425. {
  426.  char ws[128],*wsp;
  427.  char *ls=*s;
  428.  
  429.  wsp=ws;
  430.  *wsp++=c;
  431.  for(int i=1; i<MAXOPLEN; i++) ws[i]=exescan(&ls);
  432.  
  433.  struct opinf *lop;    /* Local pointer */
  434.  
  435.  lop=oppt;
  436.   
  437.  for(i=0; i<nop; i++)
  438.  {
  439.   if (!strncmp(lop->lit,ws,strlen(lop->lit)))
  440.      {*s+=strlen(lop->lit)-1; return(lop->num);}
  441.   lop++;
  442.  }
  443.   
  444.  return(-1);
  445. }
  446.  
  447.  
  448.  
  449. /*
  450.    This routine grabs size characters from the expression evaluator
  451.    work space.
  452. */
  453.  
  454. char *expval::exealloc(int size)
  455. {
  456.  char *temp;
  457.  
  458.  temp=exworkp; exworkp+=size;
  459.  if (exworkp>exwork+EXWORKSIZE-40) errfatal("Out of expression evaluator space");
  460.  
  461.  return(temp);
  462. }
  463.  
  464. /* 
  465.    This is a properly implemented input routine
  466.    s is the input buffer, n is the maximum number of characters.
  467.    
  468.    It returns the number of characters read.
  469. */
  470.  
  471. int expval::exeinput(char *s,int n)
  472. {
  473.  char *wsp,c;
  474.  
  475.  wsp=s;
  476.  
  477.  c=getch();
  478.  
  479.  while(c!=13)
  480.  {
  481.   if (!c) getch();
  482.   else
  483.   {
  484.    if ((c==8) && (wsp!=s)) {wsp--; printf("\x08 \x08");}
  485.    else if (c!=8 && (wsp-s<n)) {*wsp++=c; printf("%c",c);}
  486.   }
  487.   c=getch();
  488.  }
  489.  *wsp=0;
  490.  
  491.  return(wsp-s);
  492. }
  493.  
  494. /***********************************************************************
  495.  
  496.    Comparison Routines
  497.  
  498. ************************************************************************/
  499.  
  500. void expval::execomp(op *fo,op *so,int type)
  501. {
  502.  if (fo->optype!=so->optype) {err("Data types should match"); return;}
  503.  
  504.  float x;
  505.  
  506.  if (fo->optype==OPDATE)
  507.  {
  508.   x=atof(fo->date); fo->num=x; x=atof(so->date); so->num=x;
  509.  }
  510.  
  511.  if (fo->optype==OPSTR) x=strcmpdb(fo->str,so->str);
  512.  else x=fo->num-so->num;
  513.  
  514.  switch(type)
  515.  {
  516.   case NE2    :
  517.   case NE       : fo->num=x; break;
  518.   case EQUAL    : fo->num=!x; break;
  519.   case GT    : fo->num=(x>0); break;
  520.   case LT    : fo->num=(x<0); break;
  521.   case GTE    : fo->num=(x>=0); break;
  522.   case LTE    : fo->num=(x<=0); break;
  523.  }
  524.  
  525.  if (fo->num) fo->num=-1;
  526.  fo->optype=OPLOG;
  527. }
  528.  
  529. /*************************************************************************
  530.  
  531.  DATE Functions
  532.  
  533. **************************************************************************/
  534.  
  535. // Get day of week (character)
  536.  
  537. char *explitday[]={"Sunday","Monday","Tuesday","Wednesday",
  538.                   "Thursday","Friday","Saturday"};
  539.  
  540. char *expval::cdow(char *date) {return explitday[dow(date)-1];}
  541.  
  542. // Get day of week (numeric)
  543.  
  544. int expval::dow(char *date) {return((long)(days(date)+5-DATEOFF)%7);}
  545.  
  546. // Get month (character)
  547.  
  548. char *explitmon[]={"January","February","March","April","May","June","July",
  549.           "August","September","October","November","December"};
  550.  
  551. char *expval::cmonth(char *date)
  552. {
  553.  int x,y,z;
  554.  
  555.  gnums(date,x,y,z);
  556.  return(explitmon[y-1]);
  557. }
  558.  
  559. // Days since year 0 + dBase offset
  560.  
  561. int expvaldm[]={0,31,59,90,120,151,182,213,243,274,304,334};
  562.  
  563. long expval::days(char *date)
  564. {
  565.  int d,m,y;
  566.  
  567.  gnums(date,d,m,y);
  568.  
  569.  int ly=(!(y%4) && (y%400));    // Leap year flag
  570.  
  571.  return((long)d+long(expvaldm[m-1])-(long)(ly && m<3)+
  572.     (long)((long)y*1461L)/4L+DATEOFF);
  573. }
  574.  
  575. // Convert days since year 0 into a date
  576.  
  577. void expval::daystodate(char *date,long days)
  578. {
  579.  int d,m,y;
  580.  int dintoy;
  581.  
  582.  days-=DATEOFF;        // dBase offset
  583.  
  584.  y=(long)((days*4L)/1461L);
  585.  int ly=(!(y%4) && (y%400));    // Leap year flag
  586.  
  587.  dintoy=days-(long)((y*1461L)/4L);
  588.  
  589.  m=0; while(dintoy>expvaldm[m]-(ly && m<2)) m++;
  590.  
  591.  d=dintoy-expvaldm[m-1]+(ly && m<3);
  592.  sprintf(date,"%04d%02d%02d",y,m,d);
  593. }
  594.  
  595. // Convert character string to date (eg "19/11/62" in UKDATE format)
  596.  
  597. void expval::ctod(char *date,char *s)
  598. {
  599.  char *sp=s;
  600.  int d,m,y;
  601.  
  602.  if (db->dateformat==UKDATE)
  603.  {
  604.   d=atoi(sp); while(isdigit(*sp++));
  605.   m=atoi(sp); while(isdigit(*sp++));
  606.   y=atoi(sp);
  607.  }
  608.  else
  609.  {
  610.   m=atoi(sp); while(isdigit(*sp++));
  611.   d=atoi(sp); while(isdigit(*sp++));
  612.   y=atoi(sp);
  613.  }
  614.  
  615.  if (!y || !m || !d) {strcpy(date,"  /  /  "); return;}
  616.  
  617.  if (!(y/100)) y+=1900;
  618.  
  619.  sprintf(date,"%04d%02d%02d",y,m,d);
  620. }
  621.  
  622. /******************** SOUNDEX *******************************************/
  623.  
  624. // Provide soundex of character
  625.  
  626. char sndx[]={0,1,2,3,0,1,2,0,0,2,2,4,5,5,0,1,2,6,2,3,0,1,0,2,0,2};
  627.  
  628. char* FD soundex(char *d,char *s)
  629. {
  630.  char sl[128];
  631.  char *sp;
  632.  
  633.  sp=strupr(strncpy(sl,s,126)); sl[127]=0;
  634.  
  635.  strcpy(d,"0000");
  636.  while(isspace(*sp)) sp++;
  637.  if (!isalpha(*sp)) return d;
  638.  
  639.  *d=*sp++;
  640.  for(int i=1; i<4; i++)
  641.  {
  642.   while(*sp && isalpha(*sp) && (!sndx[*sp-'A'] || *(sp+1)==*sp)) sp++;
  643.   if (!*sp || !isalpha(*sp)) break;
  644.   d[i]=sndx[(*sp++)-'A']+'0';
  645.  }
  646.  return d;
  647. }
  648.  
  649. /******************** Error Handling ************************************/
  650.  
  651. void expval::err(char *s)
  652. {
  653.  strcpy(db->ers,s); erflag=1;
  654. }
  655.  
  656. void expval::errfatal(char *s)
  657. {
  658.  fprintf(stderr,"Expression evaluator fatal error - %s",s);
  659.  erflag=2;
  660. }
  661.  
  662.